﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.IO;
using System.Threading;
using System.Collections;
using System.Collections.Specialized;
using System.Drawing;
using System.Text.RegularExpressions;

namespace Snom
{
    /// <summary>
    /// Snone Phone
    /// </summary>
    public class SnomPhone : IDisposable
    {
        private EventsManager _eventsManager;
        private CommandsSender _commandsSender;

        public event SnomEventHandler DndOn;
        public event SnomEventHandler DndOff;
        public event SnomEventHandler CallForwardingOn;
        public event SnomEventHandler CallForwardingOff;
        public event SnomEventHandler IncomingCall;
        public event SnomEventHandler OutgoingCall;
        public event SnomEventHandler SetupFinished;
        public event SnomEventHandler Offhook;
        public event SnomEventHandler Onhook;
        public event SnomEventHandler MissedCall;
        public event SnomEventHandler RegistrationFailed;
        public event SnomEventHandler Connected;
        public event SnomEventHandler Disconnected;
        public event SnomEventHandler Logon;
        public event SnomEventHandler LogOff;
        public event SnomEventHandler HoldCall;
        public event SnomEventHandler UnholdCall;
        public event SnomEventHandler TransferCall;
        public event SnomEventHandler BlindTransfer;
        public event SnomEventHandler AttendedTransfer;
        public event SnomEventHandler ReceivedAttendedTransfer;

        public event SnomGenericEventHandler AllEvents;

        /// <summary>
        /// Fired when exception is thrown in the background thread that listens to the phone events.
        /// </summary>
        public event ErrorEventHandler Error;

        public SnomPhone(string phoneIpAddress)
            : this(phoneIpAddress, null, null, null, false, 0)
        {
        }

        public SnomPhone(string phoneIpAddress, bool enableEvents, int eventsListeningPort)
            : this(phoneIpAddress, null, null, null, enableEvents, eventsListeningPort)
        {
        }

        public SnomPhone(string phoneIpAddress, Uri proxyUri)
            : this(phoneIpAddress, null, null, proxyUri, false, 0)
        {
        }

        public SnomPhone(string phoneIpAddress, string username, string password)
            : this(phoneIpAddress, username, password, null, false, 0)
        {
        }

        public SnomPhone(string phoneIpAddress, string username, string password, Uri proxyUri)
            : this(phoneIpAddress, username, password, proxyUri, false, 0)
        {
        }

        public SnomPhone(string phoneIpAddress, string username, string password, Uri proxyUri, bool enableEvents, int eventsListeningPort)
        {
            PhoneIpAddress = phoneIpAddress;
            Username = username;
            Password = password;
            ProxyUri = proxyUri;
            IsEventsEnabled = enableEvents;

            _commandsSender = new CommandsSender(phoneIpAddress, username, password, proxyUri);

            if (IsEventsEnabled)
            {
                _eventsManager = new EventsManager(PhoneIpAddress, Username, Password, eventsListeningPort);
                _eventsManager.EventArrived += new SnomGenericEventHandler(EventsManager_EventArrived);
                _eventsManager.Error += new ErrorEventHandler(EventsManager_Error);
            }
        }

        private void EventsManager_Error(object sender, ErrorEventArgs e)
        {
            RaiseError(e.Error);
        }

        private void UnregisterEvents()
        {
            if (_eventsManager != null)
            {
                _eventsManager.UnregisterEvents();
            }
        }

        private void EventsManager_EventArrived(object sender, SnomGenericEventArgs e)
        {
            if (AllEvents != null)
            {
                AllEvents(this, e);
            }

            switch (e.EventType)
            {
                case SnomEventType.DndOn:
                    IsDndEnabled = true;
                    RaiseEvent(DndOn, e);
                    break;

                case SnomEventType.DndOff:
                    IsDndEnabled = false;
                    RaiseEvent(DndOff, e);
                    break;

                case SnomEventType.CallForwardingOn:
                    IsCallForwardingEnabled = true;
                    RaiseEvent(CallForwardingOn, e);
                    break;

                case SnomEventType.CallForwardingOff:
                    IsCallForwardingEnabled = false;
                    RaiseEvent(CallForwardingOff, e);
                    break;

                case SnomEventType.IncomingCall:
                    RaiseEvent(IncomingCall, e);
                    break;

                case SnomEventType.OutgoingCall:
                    RaiseEvent(OutgoingCall, e);
                    break;

                case SnomEventType.SetupFinished:
                    RaiseEvent(SetupFinished, e);
                    break;

                case SnomEventType.Offhook:
                    RaiseEvent(Offhook, e);
                    break;

                case SnomEventType.Onhook:
                    RaiseEvent(Onhook, e);
                    break;

                case SnomEventType.MissedCall:
                    RaiseEvent(MissedCall, e);
                    break;

                case SnomEventType.RegistrationFailed:
                    RaiseEvent(RegistrationFailed, e);
                    break;

                case SnomEventType.Connected:
                    IsConnected = true;
                    RaiseEvent(Connected, e);
                    break;

                case SnomEventType.Disconnected:
                    IsConnected = false;
                    RaiseEvent(Disconnected, e);
                    break;

                case SnomEventType.Logon:
                    RaiseEvent(Logon, e);
                    break;

                case SnomEventType.LogOff:
                    RaiseEvent(LogOff, e);
                    break;

                case SnomEventType.HoldCall:
                    IsHolding = true;
                    RaiseEvent(HoldCall, e);
                    break;

                case SnomEventType.UnholdCall:
                    IsHolding = false;
                    RaiseEvent(UnholdCall, e);
                    break;

                case SnomEventType.TransferCall:
                    RaiseEvent(TransferCall, e);
                    break;

                case SnomEventType.BlindTransfer:
                    RaiseEvent(BlindTransfer, e);
                    break;

                case SnomEventType.AttendedTransfer:
                    RaiseEvent(AttendedTransfer, e);
                    break;

                case SnomEventType.ReceivedAttendedTransfer:
                    RaiseEvent(ReceivedAttendedTransfer, e);
                    break;
            }
        }

        private void RaiseEvent(SnomEventHandler handler, SnomEventArgs args)
        {
            if (handler != null)
            {
                handler(this, args);
            }
        }

        private void RaiseError(Exception error)
        {
            if (Error != null)
            {
                Error(this, new ErrorEventArgs(error));
            }
        }

        private bool _testEventArrived;
        
        public void TestEvents()
        {
            if (!IsEventsEnabled)
            {
                throw new Exception("Events are not enabled");
            }
            _testEventArrived = false;
            AllEvents += new SnomGenericEventHandler(SnomPhone_AllEvents_Test);
            
            SendKey(SnomKeys.Dnd);

            int timeout = 5000;
            int elapsed = 0;
            int step = 500;
            while (!_testEventArrived && elapsed < timeout)
            {
                Thread.Sleep(step);
                elapsed += step;
            }

            SendKey(SnomKeys.Dnd);

            if (!_testEventArrived)
            {
                throw new Exception("Test failed, check for firewall.");
            }
        }

        private void SnomPhone_AllEvents_Test(object sender, SnomGenericEventArgs e)
        {
            if (e.EventType == SnomEventType.DndOn || e.EventType == SnomEventType.DndOff)
            {
                _testEventArrived = true;
            }
        }

        public string PhoneIpAddress
        {
            get;
            private set;
        }

        public string Username
        {
            get;
            private set;
        }

        public string Password
        {
            get;
            private set;
        }

        public bool IsEventsEnabled
        {
            get;
            private set;
        }

        public bool? IsDndEnabled
        {
            get;
            private set;
        }

        public bool? IsCallForwardingEnabled
        {
            get;
            private set;
        }

        public bool? IsConnected
        {
            get;
            private set;
        }

        public bool? IsHolding
        {
            get;
            private set;
        }
        
        public int EventsListeningPort
        {
            get
            {
                if (_eventsManager == null)
                {
                    throw new Exception("Events are not enabled.");
                }

                return _eventsManager.ListeningPort;
            }
        }

        public Uri ProxyUri
        {
            get;
            set;
        }

        /// <summary>
        /// http://wiki.snom.com/FAQ/Can_I_control_my_snom_phone_remotely
        /// </summary>
        public void ExecuteCommandSequence(string commandSequence)
        {
            _commandsSender.ExecuteCommandSequence(commandSequence);
        }

        public void DialNumber(string number)
        {
            _commandsSender.DialNumber(number);
        }

        public void SendKey(string keyName)
        {
            _commandsSender.SendKey(keyName);
        }

        public void SendDtmfKey(string keyName)
        {
            _commandsSender.SendDtmfKey(keyName);
        }

        public void ToggleMute()
        {
            _commandsSender.SendKey(SnomKeys.Mute);
        }

        public void ChangeSetting(string name, string value)
        {
            _commandsSender.ChangeSetting(name, value);
        }

        public NameValueCollection GetSettings()
        {
            return _commandsSender.GetSettings();
        }

        public string GetSetting(string name)
        {
            NameValueCollection allSettings = GetSettings();
            return allSettings[name];
        }

        public Image GetScreenDisplayImage()
        {
            return _commandsSender.GetScreenDisplayImage();
        }

        public string GetPhoneType()
        {
            return GetSetting(SnomSettingKeys.PhoneType);
        }

        public Version GetFirmwareVersion()
        {
            string version = GetSetting(SnomSettingKeys.FirmwareVersion);
            Match m = Regex.Match(version, @"(?<Version>\d+\.\d+\.\d+)");
            if (!m.Success)
            {
                throw new Exception("Invalid version: " + version);
            }
            return Version.Parse(m.Groups["Version"].Value);
        }

        public void Dispose()
        {
            if (IsEventsEnabled)
            {
                UnregisterEvents();
            }
        }
    }
}
